package core;

import material.Material;
import material.Texture;
import math.Matrix4;
import math.Vector2;
import math.Vector3;
import math.Vector4;

import java.awt.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Renderer {
    private Graphics gl;
    private Camera camera;
    private float[] curCameraPosition;
    private float[] depthData = new float[600*600];

    public Renderer(Graphics gl){
        this.gl = gl;
    }

    void initDepth(){
        for(int i=0; i<600*600; i++){
            this.depthData[i] = Float.POSITIVE_INFINITY;
        }
    }

    boolean ckeckAndsetDepth(int x, int y, float data){
        float oldData = this.depthData[y*600+x];
        if(data<oldData){
            this.depthData[y*600+x] = data;
            return true;
        }
        return false;
    }

    public void render(Scene scene, Camera camera){
        

        this.camera = camera;
        this.curCameraPosition = camera.position;

        ArrayList<Mesh> renderList = this.sortRenderList(scene);

        Graphics gl = this.gl;

        gl.setColor(Color.black);
        gl.fillRect(0,0,600,600);

        this.initDepth();

        int size = renderList.size();
        for(int i=0; i<size; i++){
            this.renderOneMesh(renderList.get(i), camera);
        }

    }

    void renderOneMesh(Mesh mesh, Camera camera){

        Geometry geometry = mesh.geometry;
        ArrayList<Integer> indices = geometry.indices;
        ArrayList<Float> vertices = geometry.vertices;
        ArrayList<Float> uv = geometry.uv;

        Material material = mesh.material;
        Texture map = material.map;
        Color color = material.color;

        Graphics ctx = this.gl;

        int size = indices.size();
        for(int i=0; i<size; i+=3){
            float point1x = (float)vertices.get(indices.get(i)*3);
            float point1y = vertices.get(indices.get(i)*3+1);
            float point1z = vertices.get(indices.get(i)*3+2);

            float point1s = uv.get(indices.get(i)*2);
            float point1t = uv.get(indices.get(i)*2+1);

            Vector3 point1 = new Vector3(point1x,point1y,point1z);
            point1.uv = new Vector2(point1s,point1t);

            float point2x = vertices.get(indices.get(i+1)*3);
            float point2y = vertices.get(indices.get(i+1)*3+1);
            float point2z = vertices.get(indices.get(i+1)*3+2);

            float point2s = uv.get(indices.get(i+1)*2);
            float point2t = uv.get(indices.get(i+1)*2+1);

            Vector3 point2 = new Vector3(point2x,point2y,point2z);
            point2.uv = new Vector2(point2s,point2t);

            float point3x = vertices.get(indices.get(i+2)*3);
            float point3y = vertices.get(indices.get(i+2)*3+1);
            float point3z = vertices.get(indices.get(i+2)*3+2);

            float point3s = uv.get(indices.get(i+2)*2);
            float point3t = uv.get(indices.get(i+2)*2+1);

            Vector3 point3 = new Vector3(point3x,point3y,point3z);
            point3.uv = new Vector2(point3s,point3t);

            this.drawTriangle(
                    point1,
                    point2,
                    point3,
                    mesh
            );
        }

    }

    Vector2 v3Tov2(Vector3 v3, Mesh mesh){


        Matrix4 vpMatrix = this.camera.VPmatrix.clone();
        Vector4 v4 = vpMatrix.concat(mesh.matrixWorld).multiplyVector4(new Vector4(v3.x, v3.y, v3.z, 1));
        Vector3 v3GL = new Vector3(v4.x/v4.w, v4.y/v4.w, v4.z/v4.w);
        Vector2 v2GL = new Vector2(v3GL.x, v3GL.y);
        Vector2 v2 = new Vector2((float)((v2GL.x/2.0+0.5)*600.0), (float)((0.5-v2GL.y/2.0)*600.0));

        v2.uv = v3.uv;
        v2.v3 = v3GL;

        return v2;
    }

    //背面剔除
    boolean checkClockwise(Vector2 p1, Vector2 p2, Vector2 p3){
        boolean isTriangleV2clockwise = false;

        if ((p2.x - p1.x) *(p3.y - p1.y) -(p3.x - p1.x) *(p2.y-p1.y) < 0.0){
            isTriangleV2clockwise = true;
        }

        return isTriangleV2clockwise;
    }

    void drawTriangle(Vector3 a,Vector3 b,Vector3 c,Mesh mesh){
        
        Graphics ctx = this.gl;

        Vector2 a1 = this.v3Tov2(a, mesh);
        Vector2 b1 = this.v3Tov2(b, mesh);
        Vector2 c1 = this.v3Tov2(c, mesh);

        if(!this.checkClockwise(a1,b1,c1)){
            return;
        }

        ArrayList<Vector2> triangleList = new ArrayList<Vector2>();
        triangleList.add(a1);
        triangleList.add(b1);
        triangleList.add(c1);

        triangleList.sort(new Comparator<Vector2>() {
            @Override
            public int compare(Vector2 o1, Vector2 o2) {
                if(o1.y < o2.y){
                    return -1;
                }else{
                    return 1;
                }
            }
        });

        Vector2 a2 = triangleList.get(0);
        Vector2 b2 = triangleList.get(1);
        Vector2 c2 = triangleList.get(2);
        ArrayList<Vector2> flatTop = new ArrayList<Vector2>(),flatBottom = new ArrayList<Vector2>();
        if(b2.y == a2.y){
            flatTop.add(c2);
            flatTop.add(a2);
            flatTop.add(b2);
        }else if(b2.y == c2.y){
            flatBottom.add(a2);
            flatBottom.add(b2);
            flatBottom.add(c2);
        }else{
            float alpha = (b2.y-a2.y)/(c2.y-a2.y);

            Vector2 d = this.getV2Linear(a2,c2,alpha);

            flatBottom.add(a2);
            flatBottom.add(b2);
            flatBottom.add(d);

            flatTop.add(c2);
            flatTop.add(b2);
            flatTop.add(d);
        }

        if(flatBottom.size()>0){
            this.drawFlatT(flatBottom,mesh);
        }
        if(flatTop.size()>0){
            this.drawFlatT(flatTop,mesh);
        }

    }

    Vector2 getV2Linear(Vector2 a2,Vector2 c2,float alpha){
        Vector2 ac = new Vector2().subVectors(c2,a2);
        Vector2 d = a2.clone().addScaledVector(ac, alpha);

        Vector2 auv = a2.uv.clone();
        Vector2 cuv = c2.uv.clone();
        Vector2 duv = new Vector2().addVectors(auv.multiplyScalar(1-alpha), cuv.multiplyScalar(alpha));
        d.uv = duv;

        Vector3 av3 = a2.v3.clone();
        Vector3 cv3 = c2.v3.clone();
        Vector3 dv3 = new Vector3().addVectors(av3.multiplyScalar(1-alpha), cv3.multiplyScalar(alpha));
        d.v3 = dv3;

        return d;
    }

//    drawT(t){
//
//        var ctx = this.gl;
//
//        var a1 = t[0];
//        var b1 = t[1];
//        var c1 = t[2];
//
//        ctx.fillStyle = '#ff0000';
//        ctx.strokeStyle = '#ff0000';
//        ctx.beginPath();
//        ctx.moveTo(a1.x,a1.y);
//        ctx.lineTo(b1.x,b1.y);
//        ctx.lineTo(c1.x,c1.y);
//        ctx.closePath();
//        ctx.stroke();
//    }

    void drawFlatT(ArrayList<Vector2> t,Mesh mesh){
        Graphics ctx = this.gl;

        Vector2 a = t.get(0);
        Vector2 b;
        Vector2 c;

        Vector2 t1 = t.get(1);
        Vector2 t2 = t.get(2);
        if(t1.x<t2.x){
            b = t1;
            c = t2;
        }else{
            c = t1;
            b = t2;
        }

        int startY = Math.round(a.y);
        int endY = Math.round(b.y);
        int height = Math.abs(endY - startY);


        int dy = 1;
        if(endY<startY){
            dy = -1;
        }

        for(int y=startY; (y-startY)*(y-endY)<=0; y+=dy){
            if(y<0||y>600){
                continue;
            }

            float alpha = (float) Math.abs(y-startY)/height;
            Vector2 startV2 = this.getV2Linear(a,b,alpha);
            Vector2 endV2 = this.getV2Linear(a,c,alpha);
            int startX = Math.round(startV2.x);
            int endX = Math.round(endV2.x);

            int width = endX - startX;
            if(width == 0){
                continue;
            }

            for(int x=startX; x<=endX; x++){
                if(x<0||x>600){
                    continue;
                }

                float alphaX = (float) Math.abs(x-startX)/width;
                Vector2 point = this.getV2Linear(startV2,endV2,alphaX);

                boolean check = this.ckeckAndsetDepth(x,y,point.v3.z);
                if(!check){
                    continue;
                }

                Color color = mesh.material.color;

//                if(color != null){
//                    color255 = [color[0]*255,color[1]*255,color[2]*255,color[3]];
//                }

//                let tex = mesh.material.map;
//                if(tex && tex.ready){
//                    color255 = tex.getColorByUV(point.uv.x,point.uv.y);
//                }

//                let r = color255[0];
//                let g = color255[1];
//                let b = color255[2];

//                ctx.fillStyle = 'rgb('+r+','+g+','+b+')';

                ctx.setColor(color);
                ctx.fillRect(x,y,1,1);
            }
        }

    }

    ArrayList<Mesh> getAllObjList(ArrayList<Mesh> obj, ArrayList<Mesh> allObjList){

        int size = obj.size();
        for(int i=0; i<size; i++){
            Mesh mesh = obj.get(i);
            if(mesh.geometry != null){
                allObjList.add(mesh);
            }
            this.getAllObjList(mesh.children, allObjList);
        }
        return allObjList;
    }

    ArrayList<Mesh> sortRenderList(Scene scene){


        ArrayList<Mesh> allObjList = this.getAllObjList(scene.children , new ArrayList<Mesh>());

//        for(var i in allObjList){
//            var pa = allObjList[i].getWorldPosition();
//            var pc = new Vector3().fromArray(this.curCameraPosition);
//            var da = pc.distanceTo(pa);
//            allObjList[i].distanceToCamera = da;
//        }
//        var allObjSortedList = allObjList.sort(this.sortFun);
//        return allObjSortedList;

        return allObjList;
    }

//    sortFun(a, b){
//        return b.distanceToCamera - a.distanceToCamera;
//    }
}
